/* 
 * USB Driver for Pandora Head - 1.0
 * Author: Elias Kyrlies-Chrysoulidis
 * The driver is based on the 2.2 version of drivers/usb/usb-skeleton.c 
 */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/mutex.h>

MODULE_LICENSE("GPL");
#define ATMEL_VENDOR_ID 0x03EB
#define ATMEL_PRODUCT_ID 0x2423
#define USB_HEAD_VENDOR_ID ATMEL_VENDOR_ID /* Atmel Vendor ID assigned by USB org */
#define USB_HEAD_PRODUCT_ID ATMEL_PRODUCT_ID /* Atmel ASF vendor class PID */


/* USB ID Table specifying the devices that this driver supports */
/* When the USB core detects a device with properties matching the ones
   decleared in usb_device_id table belonging to a client driver it invokes
   the probe method registered by that driver */
/* If you don't do this then it might get probed with every new device */
static struct usb_device_id head_ids[] = {
	{ USB_DEVICE(USB_HEAD_VENDOR_ID, USB_HEAD_PRODUCT_ID)},
	{}			/* Terminate */
};
/* You need to create this in order to load the driver automatically when
   device plugged in */
MODULE_DEVICE_TABLE(usb, head_ids);


/* 
   -------------------------------------------------------------------------------
*/

/* Device-specific structure, available in /dev/head file->private_data */
typedef struct {
	struct usb_device *usbdev;       /* Device representation */
	struct usb_interface *interface; /* Interface representation */
	struct urb *ctrl_urb;            /* Control URB for register access  */
	struct usb_ctrlrequest ctrl_req; /* Control request as per the spec */
	unsigned char *int_in_buf;	 /* Receive data buffer */
	size_t int_in_len;		 /* Receive buffer size */
	__u8 int_in_addr;		 /* IN endpoint address */
	__u8 int_out_addr;		 /* OUT endpoint address */
	/* ... */
} head_device_t;

#define HEAD_MINOR_BASE 0xC0	/* Assigned by the Linux - USB subsystem maintainer */


/*--------------------Various request definitions--------------------------*/
#define HEAD_CONFIG_REG_OFFSET 0x00
/* Value to program in the configuration register */
#define HEAD_CONFIG_REG_VALUE 0xB0

/* The vendor-defined bRequestType */
#define HEAD_REQUEST_RECIPIENT 0X03 
/* Available RequestTypes:
   enum{
   USB_REQ_RECIP_DEVICE,  
   USB_REQ_RECIP_INTERFACE,
   USB_REQ_RECIP_ENDPOINT,
   USB_REQ_RECIP_OTHER,
   USB_REQ_RECIP_MASK = 0x1F }*/


/* The vendor-defined bRequest */
#define HEAD_BREQUEST 0x00
/* Available Requests: 
   enum{
   USB_REQ_GET_STATUS, 
   USB_REQ_CLEAR_FEATURE, 
   USB_REQ_SET_FEATURE = 3,
   USB_REQ_SET_ADDRESS = 5,
   USB_REQ_GET_DESCRIPTOR,
   USB_REQ_SET_DESCRIPTOR,
   USB_REQ_GET_CONFIGURATION,
   USB_REQ_SET_CONFIGURATION,
   USB_REQ_GET_INTERFACE,
   USB_REQ_SET_INTERFACE,
   USB_REQ_SYNCH_FRAME}*/


static struct usb_driver head_driver;

/* --------- Entry Points --------- */

/* Callback function */
static void
head_ctrl_callback(struct urb *urb)
{
	printk(KERN_WARNING "Ctrl URB submitted\n");
	complete((struct completion *)urb->context );
}

/* Open Method */
/*
  This method defines a hook for the /dev/head open function.
  The most of the functionality defined here is for demonstration
  purposes and can be ommited, since it does not serve a particular need 
  of our application (Pandora head). However it is appended here as a good 
  example of how to send setup requests to the device. As far as our application
  needs, simply set file->private_data = head_device, so that it can be used from write 
  and read.
*/

static int
head_open(struct inode *inode, struct file *file)
{
	struct completion head_config_done;//used for the urb completion mechanism
	head_device_t *head_device;
	void *head_ctrl_context;
	char *tmp;
	__le16 head_config_index = 0;//HEAD_CONFIG_REG_OFFSET;
	unsigned int head_ctrl_pipe;
	struct usb_interface *interface;
	

	/* Obtain the pointer to the device-specific structure.
	   We saved it using usb_set_intfdata() in head_probe()*/
	interface = usb_find_interface(&head_driver, iminor(inode));

	if (!interface) {
		err("%s - error, can't find device for minor %d", __func__,
		    iminor(inode));
		return -ENODEV;
	}
	
	head_device = usb_get_intfdata(interface);
	
	
	/* Allocate a URB for the control transfer */
	head_device->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL);
	if(!head_device->ctrl_urb) {
		return -EIO;
	}

	/* Populate the Control Request */
	head_device->ctrl_req.bRequestType = HEAD_REQUEST_RECIPIENT;
	head_device->ctrl_req.bRequest = HEAD_BREQUEST;
	head_device->ctrl_req.wValue = cpu_to_le16(HEAD_CONFIG_REG_VALUE);
	head_device->ctrl_req.wIndex = cpu_to_le16(head_config_index);
	head_device->ctrl_req.wLength = cpu_to_le16(1);
	head_device->ctrl_urb->transfer_buffer_length = 1;
	tmp = kmalloc(1, GFP_KERNEL);
	*tmp = HEAD_CONFIG_REG_VALUE;
	
	/* Create a control pipe attached to endpoint 0 */
	head_ctrl_pipe = usb_sndctrlpipe( head_device->usbdev, 0);
	
	/* Initialize the completion mechanism. Call kernel completion API */
	init_completion(&head_config_done);
	
	/* Set URB context. The context is part of the URB that is passed
	   to the callback function as an argument. In this case, the context
	   is the completion structure, head_config_done */
	head_ctrl_context = (void *) &head_config_done;
	
	/* Initialize the field in the control URB */
	usb_fill_control_urb(head_device->ctrl_urb, head_device->usbdev, head_ctrl_pipe, (char *) &head_device->ctrl_req, tmp, 1, head_ctrl_callback, head_ctrl_context);
	
	/* Submit the URB */
	usb_submit_urb( head_device->ctrl_urb, GFP_ATOMIC);
	
	/* Wait until the callback returns indicating that the head configu
	   ration ragister has been succesfully initialized */
	wait_for_completion(&head_config_done);
	
	/* Release our reference to the URB */
	usb_free_urb(head_device->ctrl_urb);
	kfree(tmp);
	
	/* Save the device-specific object to the files private_data
	   so that you can directly retrieve it from other entry points such
	   as head_read() and head_write()*/
	file->private_data = head_device;

	printk(KERN_WARNING "head_open invoked. Hurahhh! %d\n",head_device->ctrl_urb->status);
	/* Return the URB transfer status */
	return (head_device->ctrl_urb->status); 
}

/* Read callback */
/* Used only in asynchronous read */
static void
head_read_callback(struct urb *urb){
	
}

/* Read entry point */
static ssize_t
head_read( struct file *file, char *buffer, size_t count, loff_t *ppos)
{
	int retval, bytes_read;
	head_device_t *head_device;
	/* struct urb *urb = NULL; */
	/* char *head_buf = NULL; */
	/* Get the address of head_device */
	head_device = (head_device_t *)file->private_data;
	
	/* ... */
	/* /\* -----------Asynchronus read----------- *\/ */
	/* /\* Allocate an int URB *\/ */
	/* urb = usb_alloc_urb(0, GFP_KERNEL); */
	/* if (!urb) { */
	/* 	return -ENOMEM; */
	/* } */

	/* /\* Allocate DMA-consistent transfer buffer and copy data */
	/*    to userspace. On return, head_buf contains the CPU address, */
	/*    while urb->transfer_dma contains the DMA address*\/ */
	/* head_buf = usb_buffer_alloc(head_device->usbdev, count, GFP_KERNEL, */
	/* 			    &urb->transfer_dma); */

	/* /\* prepare a read *\/ */
	/* usb_fill_int_urb(urb, head_device->usbdev, */
	/* 		 usb_rcvintpipe(head_device->usbdev, */
	/* 				head_device->int_in_addr), */
	/* 		 head_device->int_in_buf, */
	/* 		 min(head_device->int_in_len,count), */
	/* 		 head_read_callback, */
	/* 		 head_device); */
	
	/* /\* Tell everybody to leave this urb alone *\/ */
	/* //something to borrow from usb-skeleton.c here */
	
	/* /\* submit it *\/ */
	/* retval = usb_submit_urb(urb, GFP_KERNEL); */
	/* if (retval */
	

	/* ----------------- Synchronus read -------------- */
	retval = usb_interrupt_msg(head_device->usbdev, /* usb_device */
				   usb_rcvintpipe(head_device->usbdev,
						  head_device->int_in_addr), /* pipe */
				   head_device->int_in_buf, /* Read buffer */
				   min(head_device->int_in_len, count), /* Bytes to read */
				   &bytes_read, /* Bytes read */
				   5000);
	printk(KERN_WARNING "int_in_len %d", head_device->int_in_len);
	printk(KERN_WARNING "bytes read %d", bytes_read);
	printk(KERN_WARNING "Wra gia diabasma %s", head_device->int_in_buf);

	/* Copy head data to user space */
	if (!retval) {
		if(copy_to_user(buffer, head_device->int_in_buf, bytes_read)){
			return -EFAULT;
		} else {
			return bytes_read;
		}
	}
	return retval;
}

/* Write callback */
static void
head_write_callback(struct urb *urb)
{
	

	head_device_t *head_device;
	/* Get the address of head_device */
	head_device = (head_device_t *)urb->context;
	
	/* urb->status conatins the subission status. Its 0 if
	   succesful. Resubmit the URB in case of errors other than
	   -ENOENT, -ECONNRESET, and -ESHUTDOWN */
	if (urb->status) {
		if(!(urb->status == -ENOENT ||
		     urb->status == -ECONNRESET ||
		     urb->status == -ESHUTDOWN))
			err("%s - nonzero write bulk status received: %d",
			    __func__, urb->status);
	}
	/* ... */
	
	/* Free the transfer buffer. usb_buffer_free() is the 
	   release-counerpart of usb_buffer_alloc() caclled form head_write  */
	
	printk(KERN_WARNING "Write callback called\n");

	usb_buffer_free(urb->dev, urb->transfer_buffer_length, 
			urb->transfer_buffer, urb->transfer_dma);
}	

/* Write entry point */
static ssize_t
head_write(struct file *file, const char *buffer, size_t write_count, loff_t *ppos)
{
	int retval;
	char *head_buf = NULL;
	struct urb *urb = NULL;
	head_device_t *head_device;
	
	struct usb_host_interface *iface_desc;
	/* Get the address of head_device */
	head_device = (head_device_t *)file->private_data;
		
	/* ... */

	/* Allocate a int URB */
	urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!urb) {
		return -ENOMEM;
	}

	/* Allocate a DMA-consistent transfer buffer and copy in 
	   data from user space. On return, head_buf contains the buffers 
	   CPU address, while urb->transfer_dma contains the DMA address*/

	head_buf = usb_buffer_alloc(head_device->usbdev, write_count, GFP_KERNEL,
				    &urb->transfer_dma);
	
	//fill in the urb buffer with usrspace buffer data
	if (copy_from_user(head_buf, buffer, write_count)){
		usb_buffer_free(head_device->usbdev, write_count, head_buf, urb->transfer_dma);
		usb_free_urb(urb);
		return -EFAULT;
	}

	/*-----------------using usb_interrupt_msg--------------*/
	/* retval = usb_interrupt_msg( head_device->usbdev,  */
	/* 			    usb_sndintpipe(head_device->usbdev,  */
	/* 					   head_device->int_out_addr), */
	/* 			    head_buf, write_count, &write_count, 1000); */
	/* /\* Copy head data to user space *\/ */
	/* printk(KERN_WARNING "Write URB submitddddddted %u\n", head_device->int_out_addr);// iface_desc->endpoint[1].desc.bEndpointAddress); */
	/* if (!retval) { */
	/* 	return write_count; */
	/* } */
	/* return retval; */
	

	/*-----------------using interrupt urb-----------------*/
	/* Populate int URB fields */
	usb_fill_int_urb(urb, head_device->usbdev,
			 usb_sndintpipe(head_device->usbdev,
					head_device->int_out_addr),
			 head_buf, write_count, head_write_callback,
			 head_device, 32);
	
	/* urb->transfer_dma is valid, so preferably utilize that for
	   data transfer*/
	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
	
	iface_desc = head_device->interface->cur_altsetting;
	/* Submit URB asynchronously */
	usb_submit_urb(urb, GFP_KERNEL);
	printk(KERN_WARNING "Write URB submitddddddted %u\n", head_device->int_out_addr);// iface_desc->endpoint[1].desc.bEndpointAddress);
	/* Release URB reference */
	usb_free_urb(urb);
	return(write_count);
}



/* ------------------------------------------------------------------------- */


/* 
 * Conventional char driver entry points. 
 */

static struct file_operations head_fops =
{
	.owner = THIS_MODULE,	/* Owner */
	.read = head_read,	/* Read method */
	.write = head_write,	/* Write method */
	/* .ioctl = head_ioctl,	/\* Ioctl method *\/ */
	.open = head_open,	/* Open method */
	/* .release = head_release, /\* Close method *\/ */
};

/* struct used to (de)register device in /dev/ */
static struct usb_class_driver head_class = {
	.name = "head",		/* Owner */
	.fops = &head_fops,	/* Conect with /dev/head */
	.minor_base = HEAD_MINOR_BASE, /* Minor nubmer start */
};

/* ------------------------------------------------------------------------- */

/* The probe() method is invoked by khubd after device
   enumeration. The first argument interface, contains 
   information gleaned during the enumeration process. 
   id is the entry in the drivers usb_device_id table that 
   matches the values read from the head card. head_brobe() is
   based on skel_probe()
   defined in drivers/usb/usb-skeleton.c */

static int
head_probe( struct usb_interface *interface, const struct usb_device_id *id)
{

	
	struct usb_host_interface *iface_desc;  // this will hold cur_altsetting
	struct usb_endpoint_descriptor *endpoint;
	head_device_t *head_device;
	int retval = -ENOMEM;
	int i;
	

	/* Allocate the device-specific structure */
	head_device = kzalloc(sizeof(head_device_t), GFP_KERNEL);

	/* Fill the usb_device and usb_interface */
	head_device->usbdev = usb_get_dev(interface_to_usbdev(interface));
	head_device->interface = interface;


	/* Set up endpoint information from the data gleaned 
	   during device enumeration */
	iface_desc = interface->cur_altsetting;

	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i){
		endpoint = &iface_desc->endpoint[i].desc;
		if (!head_device->int_in_addr && \
		    usb_endpoint_is_int_in(endpoint)){
			/* Int IN endpoint */
			head_device->int_in_len = le16_to_cpu(endpoint->wMaxPacketSize); /* little endian 16 to cpu -  From enpoint descriptor */
			head_device->int_in_addr = endpoint->bEndpointAddress;
			head_device->int_in_buf = kmalloc( head_device->int_in_len, GFP_KERNEL);
		}else if(!head_device->int_out_addr && usb_endpoint_is_int_out(endpoint)){
			/* Int OUT endpoint */
			head_device->int_out_addr = endpoint->bEndpointAddress;

		}
	}

	

	/* If it is none of them */
	if (!(head_device->int_in_addr && head_device->int_out_addr)){
		/* edw mas epistrefei o LaCie */
		return retval;
	}

	/* Attach the device-specific structure to this interface. We will
	   retrieve it from head_open() */
	/* This happens because interface has been an argument to the fnctn */
	usb_set_intfdata(interface, head_device); /* Keeps pointer to
						     structure 
						     head_device to 
						     interface->data 
						     in order to be 
						     accesible 
						     from other
						     methods as well*/

	printk(KERN_ALERT "PROBE MUHAHA\n");

	
	/* Register the device under the /dev/ fs*/
	retval = usb_register_dev(interface, &head_class); 
	printk(KERN_WARNING "%d",retval);
	if (retval) {
		/* something prevented us from registering this driver */
		dev_err(&interface->dev, "Not able to get a minor for this device.\n");
		usb_set_intfdata(interface, NULL);
		return retval;
	}

	dev_info(&interface->dev,
		 "USB head device now attached to head-%d\n",
		 interface->minor);

	
	return 0;
}


/* Disconnect method. Called when the device is unplugged or when the module is   unloaded */

static void
head_disconnect( struct usb_interface *interface)
{
	head_device_t *head_device;
	/* ... */
	/* Reverse of usb_set_intfdata() invoked from head_probe() */
	head_device = usb_get_intfdata(interface);

	/* Zero out interface data */
	usb_set_intfdata(interface, NULL);

	/* NULL the interface. In the real world, protect this operation 
	   using locks */
	head_device->interface = NULL;
	usb_deregister_dev(interface, &head_class);

	/* ... */
	printk(KERN_WARNING "DISC bye bye device\n");
}

/* The usb_driver structure for this driver */
/* Must provide name, probe, disconnect, id_table. Other
   fields are optional */
static struct usb_driver head_driver = 
{
	.name = "head",		/* Unique name */
	.probe = head_probe,	/* see Listing 11.3 */
	.disconnect = head_disconnect, /* See Listing 11.3 */
	.id_table = head_ids,	       /* See above */

};

/* Module Initialization */
static int __init
usb_head_init(void)
{
	int result;
	
	/* Register with the USB core */
	result = usb_register(&head_driver);
	printk(KERN_ALERT "INIT MPHKA\n");
	/* ... */
	return 0;
}	

/* Module Exit */
static void __exit
usb_head_exit(void)
{
	/* Unregister from the USB core */
	usb_deregister(&head_driver);
	printk(KERN_ALERT "Bye Bye cruel world\n");
	return;
}



module_init(usb_head_init);
module_exit(usb_head_exit);

